/*
 * Camera.cpp
 *
 *  Created on: Sep 30, 2009
 *      Author: tiago.zaccaro
 */

#include "Graphics/Camera.h"

namespace AcquaEngine {

namespace Graphics {

Camera::Camera(Ogre::Camera * camera) :
	mCamera(camera)
	,mCameraNode(0)
	,mCameraYawNode(0)
	,mPitchLimit(Ogre::Degree(80))
{
	if (mCamera)
	{
		// Note: we use the SceneManager this Camera is attached to.
		mCameraNode = camera->getSceneManager()->getRootSceneNode()->createChildSceneNode("CameraNode_");
		mCameraYawNode = mCameraNode->createChildSceneNode("CameraYawNode_");
		mCameraYawNode->attachObject(mCamera);

		mCamera->setPosition( Ogre::Vector3::ZERO );

		//mCamera->setQueryFlags( QueryFlags::Camera );
	}
	else
	{
		throw std::runtime_error("Error, no camera supplied.");
	}
}

Camera::~Camera()
{
	mCameraYawNode->detachObject(mCamera);
	mCameraNode->removeChild(mCameraYawNode);

	mCamera->getSceneManager()->destroySceneNode(mCameraYawNode->getName());
	mCamera->getSceneManager()->destroySceneNode(mCameraNode->getName());
	mCameraYawNode = 0;
	mCameraNode = 0;

	mCamera->getSceneManager()->destroyCamera(mCamera);
}

void Camera::pitch(const Ogre::Real degrees)
{
	mCamera->pitch( Ogre::Radian(degrees) );

	Ogre::Quaternion pitch = mCamera->getOrientation();

	if (pitch.getPitch() > mPitchLimit)
	{
		mCamera->setOrientation( Ogre::Quaternion::IDENTITY );
		mCamera->pitch( mPitchLimit );
	}
	else if (pitch.getPitch() < -mPitchLimit)
	{
		mCamera->setOrientation( Ogre::Quaternion::IDENTITY );
		mCamera->pitch( -mPitchLimit );
	}
}

void Camera::yaw(const Ogre::Real degrees)
{
	//m_CameraYawNode->yaw( Ogre::Radian(degrees) );
	//b1n - Changed to support the new spectator cam
	mCameraNode->yaw( Ogre::Radian(degrees) );
}

void Camera::roll(const Ogre::Real degrees)
{
	//Logger::debug("Camera::roll not implemented by design.");
}

Ogre::Vector3 Camera::getPosition()
{
	return mCameraNode->getPosition();
}

void Camera::setPosition(const Ogre::Vector3 &position)
{
	mCameraNode->setPosition(position);
}

void Camera::moveRelative(const Ogre::Vector3& vector)
{
	Ogre::Vector3 trans = getOrientation() * vector;
	setPosition( getPosition() + trans );
}

void Camera::moveRelativeToCamera(const Ogre::Vector3& vector)
{
	Ogre::Vector3 trans = getCameraOrientation() * vector;
	setPosition( getPosition() + trans );
}

Ogre::Vector3 Camera::getDirection()
{
	return mCamera->getDerivedOrientation() * -Ogre::Vector3::UNIT_Z;
}

void Camera::setDirection(const Ogre::Vector3& direction)
{
	// This is copied straight from Ogre... thanks guys.

	// Do nothing if given a zero vector
	// (Replaced assert since this could happen with auto tracking camera and
	//  camera passes through the lookAt point)
	if (direction == Ogre::Vector3::ZERO) return;

	// Remember, camera points down -Z of local axes!
	// Therefore reverse direction of direction vector before determining local Z
	Ogre::Vector3 zAdjustVec = -direction;
	zAdjustVec.normalise();

	// Get axes from current quaternion
	Ogre::Vector3 axes[3];
	mCameraNode->_getDerivedOrientation().ToAxes(axes[0], axes[1], axes[2]);

	Ogre::Quaternion rotQuat;
	if ( (axes[2]+zAdjustVec).squaredLength() <  0.00005f)
	{
		// Oops, a 180 degree turn (infinite possible rotation axes)
		// Default to yaw i.e. use current UP
		rotQuat.FromAngleAxis(Ogre::Radian( Ogre::Math::PI ), axes[1]);
	}
	else
	{
		// Derive shortest arc to new direction
		rotQuat = axes[2].getRotationTo(zAdjustVec);
	}
	mCameraNode->setOrientation( rotQuat * mCameraNode->getOrientation());

	// transform to parent space
	if (mCameraNode->getParent())
	{
		mCameraNode->setOrientation(
				mCameraNode->getParent()->_getDerivedOrientation().Inverse()
					* mCameraNode->getOrientation()
			);
	}

	// Reset the Camera and CameraYawNode orientations.
	mCamera->setOrientation( Ogre::Quaternion::IDENTITY );
	mCameraYawNode->setOrientation( Ogre::Quaternion::IDENTITY );
}

Ogre::Quaternion Camera::getOrientation()
{
	return mCameraNode->getOrientation();
}

Ogre::Quaternion Camera::getCameraOrientation()
{
	return mCamera->getDerivedOrientation();
}

void Camera::setOrientation(const Ogre::Quaternion &orientation)
{
	return mCameraNode->setOrientation(orientation);
}

Ogre::Real Camera::getPitchLimit()
{
	return mPitchLimit.valueDegrees();
}

void Camera::setPitchLimit(Ogre::Real degrees)
{
	mPitchLimit = Ogre::Degree(degrees);
}

Ogre::Vector3 Camera::getUp()
{
	return getOrientation() * Ogre::Vector3::UNIT_Y;
}

Ogre::Vector3 Camera::getRight()
{
	return mCameraYawNode->_getDerivedOrientation() * Ogre::Vector3::UNIT_X;
}

Ogre::SceneNode * Camera::getSceneNode()
{
	return mCameraNode;
}

Ogre::Camera * Camera::getCamera()
{
	return mCamera;
}

}

}
